Closures in de praktijk
Dat lijkt één van de moeilijkste aspecten van JavaScript te zijn. Closures zijn mogelijk omdat in JavaScript:
- functies data zijn;
- het bereik van variabelen op functie niveau is;
- functies een functie kunnen retourneren;
Bronnen
Zell Liew, JavaScript Scope and Closures, August 28, 2017
Inleiding
Eric Elliott, Master the JavaScript Interview: What is a Closure?, 7 januari 2016
Jeremy McPeak, How do JavaScript closures work?, Learn the logic behind closures with a practical demonstration and explanation of why they are important to maintaining privacy in your code. Video, September 12, 2016
Encapsulatie
Voorbeeld 1
Functies kunnen zichzelf uitvoeren. Dat weten we ondertussen wel:
(function() { console.log('hello'); })(); //log 'hello'
De syntax is eenvoudig. We declareren een functie en roepen die onmiddellijk op met de ()
syntaxis. De vraag is waarom we zoiets bizar zouden willen doen? Dat spreekt alles tegen wat tot nu toe over functies hebben geleerd. Een function bevat normaal gezien code die we pas later willen uitvoeren, niet op het moment van de declaratie ervan. Uitgestelde executie heet dat. Dat is een van de redenen waarom we uberhaupt de code in een functie steken.
En toch is er een goede reden om dit te doen. Namelijk als we de waarden van de variabelen willen bewaren om later in een uitgesteld functie (callback) te gebruiken. Hier is ons probleem:
var something = 'hello'; setTimeout(function() { console.log(something); }, 1000); var something = 'tot ziens';
Als je nieuw bent in JavaScript, zal je je waarschijnlijk afvragen waarom in de console 'tot ziens' gezegd wordt i.p.v. 'hello'. Dat komt om dat de timeout
callback functie precies is wat het is namelijk een callback . Deze functie niet uitgevoerd op het moment dat de setTimeOut functie wordt uitgevoerd, maar na 1 seconde. Tegen dan is de variable something allang overschreven met de waarde 'tot ziens'.
Een closure is een oplossing voor dit probleem. In plaats van dat we de time-out callback impliciet opgeven zoals we hierboven deden, retourneren we die als een IIFE waarin we de huidige waarde van something
als argument meegeven. Dat betekent dat we de huidige waarde van something
kopiëren in de functie en zo beschermen tegen om het even wat er ook gebeurt met de variabele something
.
var something = 'hello'; setTimeout(function() { console.log(something); }, 1000); setTimeout((function(something) { // hier wirdt een lokale copy // van gemaakt van de doorgegeven // parameter return function() { console.log(something); } })(something), 1000); something = 'tot ziens';
Nu wordt de tekst 'hello' getoond zoals verwacht werd omdat het de afgesloten versie van something toont, namelijk het functie-argument, en niet de buitenste variabele.
Je kan die code uitproberen op CodePen:
Voorbeeld 2: iterator
Dit voorbeeld van een sluiting illustreert hoe je met een sluiting een iterator kan implementeren.
Filmpje: JS - closure sample iterator
Je weet al hoe je met een lus een eenvoudige array kan doorlopen, maar er kunnen zich gevallen voordoen waar je niet gewoon door de array wilt lopen maar een ingewikkelder parcours wilt afleggen. Je wilt bijvoorbeeld iets doen met het n-de element maar daarvoor moet je weten wat er in het volgende of vorige element zit. We geven een voorbeeld met een array met een eenvoudige datastructuur. Hier volgt een initialisatie functie die als argument een array aanneemt en ook een geheime pointer definieert met de naam i, die altijd naar het volgende element in de array verwijst:
function init(x) { var i = 0; return function () { return x[i++]; }; }
Je maakt de functie met de naam volgende door de init() functie op de roepen en een array als argument mee te geven:
var volgende = init([1, 5, 10, 15]);
Telkens je de functie volgende()
uitvoert krijg je de waarde van het volgende element:
volgende() // totdat je aan het einde van de array komt; 1 volgende(); 5 volgende(); 10 volgende(); 15 volgende(); undefined
See the Pen Closure toepassing Iterator by Jef Inghelbrecht (@jef) on CodePen.
function refactoring
In Immediately-Invoked Function Expression hebben we functie gemaakt die zichzelf wijzigt afhankelijk van wie de bezoeker van de website is. We gingen ervan uit dat bezoeker niet verandert tijdens een sessie. Veronderstel nu dat we wel willen toelaten dat de gebruiker verandert tijdens een sessie. Welnu een closure kan de locale variabelen van de buitenste functie overschrijven en dat is precies wat we nodig hebben.
Onze buitenste functie doet twee dingen:
- declareert een lokale variable met de naam
user
- retourneert een anonieme functie
Deze anonieme functie heeft toegang tot user
alhoewel die buiten de functie is aangemaakt maar wel binnen de scope waarin de closure is gecreëerd.
Closures kunnen meer dan alleen maar de lokale variabelen van hun buitenste functies lezen. Ze kunnen die ook wijzigen: